package com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.global;

import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.VariableAPI.MathUtil.TimeUtil;
import com.nulldev.util.data.Arrays.maps.sem.SelfExpiringHashMap;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.BaseJSEnvironment;
import com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.BaseJSInterp;
import com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.BaseJSObject;
import com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.BaseJSVariable;

public abstract class BaseJSNativeFunction extends BaseJSObject implements Cloneable {
	public final String[] arguments;

	public final Map<String, BaseJSVariable> valueCache = (Arguments.hasArgument("--BaseJS.enableGC")
			? new SelfExpiringHashMap<String, BaseJSVariable>(TimeUtil.fromMinutes(5))
			: new ConcurrentHashMap<String, BaseJSVariable>());
	public volatile Object returnValue;
	private static final Logger log = BaseJSInterp.log;

	public BaseJSNativeFunction(final String name, final String... arguments) {
		super(name);
		this.arguments = arguments;
	}

	public Object run(final boolean USE_NEW_INTERPRETER, final BaseJSEnvironment e, final Object... variables) {
		return this.run(e, variables);
	}

	public Object run(final BaseJSEnvironment e, final Object... variables) {
		BaseJSNativeFunction self = null;
		try {
			self = this.clone();
		} catch (CloneNotSupportedException e1) {
			throw new RuntimeException(e1);
		}
		if (variables != null && variables.length > 0) {
			try {
				for (int i = 0; i < variables.length; i++) {
					self.valueCache.put(arguments[i], new BaseJSVariable(name, variables[i]));
				}
			} catch (Exception ef) {
				if (variables.length > arguments.length && arguments.length == 0) {
				} else if (variables.length > arguments.length)
					log.warn(this.name + "() -> Argument count mismatch! [variables: " + variables.length + ", expected: " + arguments.length + "]");
				else {
					log.error("Failed to run: " + toString() + ", arguments: " + Arrays.toString(variables));
					throw new UnsupportedOperationException(
							"Failed to load function! [variables_expected: " + arguments.length + ", variables_given: " + variables.length + "]", ef);
				}
			}
		}
		return this.invoked(e, self, variables);
	}

	public abstract Object invoked(final BaseJSEnvironment e, final BaseJSNativeFunction f, final Object... variables);

	@Override
	protected BaseJSNativeFunction clone() throws CloneNotSupportedException {
		return new BaseJSNativeFunction(this.name, this.arguments) {

			@Override
			public Object invoked(BaseJSEnvironment e, BaseJSNativeFunction f, Object... variables) {
				return BaseJSNativeFunction.this.invoked(e, f, variables);
			}

		};
	}

	@Override
	public String toString() {
		return "BaseJSNativeFunction[name: " + name + ", arguments: " + Arrays.toString(arguments) + "]";
	}
}
